﻿using System;
using System.Collections.Generic;
using PostRequestFactory.Core.Interfaces;
using PostRequestFactory.Core.Parameters;

namespace PostRequestFactory.Core
{
    /// <summary>
    /// This class is used for creating
    /// PostRequest instances
    /// </summary>
    public class RequestFactory
    {
        #region String constants
        
        private const string BoundaryDefinitionTemplate = "boundary={0}";
        private const string BeginBoundaryTemplate = "--{0}\r\n";
        private const string EndBoundaryTemplate = "--{0}--\r\n";
        private const string NewLine = "\r\n";
        private const string DispositionHeaderTemplate = @"Content-Disposition: form-data; name=""{0}""";
        private const string FileNameParamTemplate = @"; filename=""{0}""";
        private const string TypeHeaderTemplate = " {0}; ";
        private const string BodyTypeHeaderTemplate = "Content-Type: {0};\r\n";
        private const string TransportEncodingHeaderTemplate = "Content-Transfer-Encoding: {0};\r\n\r\n"; 

        #endregion

        /// <summary>
        /// Creates a post request based on the provided parameters
        /// </summary>
        /// <param name="requestParameters">List of IRequestParameters thtat will be used for request creation </param>
        /// <param name="request">An instance of PostRequest can be provided to be populated with data. Returned result will be the same instance but updated with params data.</param>
        /// <returns>An instance of PostRequest</returns>
        public static PostRequest CreateRequest(List<IRequestParameter> requestParameters, PostRequest request = null)
        {
            if (requestParameters == null || requestParameters.Count == 0)
            {
                throw new ArgumentException("You need to specify at least one parameter, otherwise it is not possible to create a post request.");
            }

            if (requestParameters.Exists(p => p.ParameterType == RequestParameterType.Basic)
               && requestParameters.Exists(p => p.ParameterType != RequestParameterType.Basic))
            {
                throw new ApplicationException("You cannot have, for the same request, a mix from basic parameters and other types. Basic parameters should not be used in combination with other types of parameters.");
            }

            if (request == null)
            {
                request = new PostRequest();
            }

            InitContentTypeHeader(request, requestParameters[0]);

            foreach (var param in requestParameters)
            {
                if (param == null)
                {
                    continue;
                }

                switch (param.ParameterType)
                {
                    case RequestParameterType.Basic:
                        AddBasicParameterToBodyContent(request, (BasicRequestParameter) param);
                        break;

                    case RequestParameterType.Text:
                        AddTextParameterToBodyContent(request, (TextRequestParameter)param);
                        break;

                    case RequestParameterType.Binary:
                        AddBinaryParameterToBodyContent(request, (BinaryRequestParameter)param);
                        break;

                    default:
                        throw new ApplicationException(string.Format("{0} is an unknown type of RequestParameter.", param.GetType().Name));
                }
            }

            //Add end boundary delimiter to the end of the request for the multipart request
            if (requestParameters[0].ParameterType != RequestParameterType.Basic)
            {
                request.BodyBuilder.Append(string.Format(EndBoundaryTemplate, request.BoundaryValue));
            }

            return request;
        }

        /// <summary>
        /// Creates a post request based on the provided parameters
        /// </summary>
        /// <param name="requestParameters">Array of IRequestParameters thtat will be used for request creation </param>
        /// <returns>An instance of PostRequest</returns>
        public static PostRequest CreateRequest(params IRequestParameter[] requestParameters)
        {
            return CreateRequest( new List<IRequestParameter>(requestParameters));
        }

        /// <summary>
        /// Create Content-Type header value based on the provided parameters
        /// </summary>
        /// <param name="request">Request for which Content-Type header is generated</param>
        /// <param name="firstRequestParameter">First parameter which was used in generating post request body</param>
        private static void InitContentTypeHeader(PostRequest request, IRequestParameter firstRequestParameter)
        {
            request.ContentTypeHeader = 
                string.Format(TypeHeaderTemplate, firstRequestParameter.ParameterType == RequestParameterType.Basic ? "application/x-www-form-urlencoded" : "multipart/form-data");

            if (firstRequestParameter.ParameterType != RequestParameterType.Basic)
            {
                request.ContentTypeHeader += string.Format(BoundaryDefinitionTemplate, request.BoundaryValue); 
            }

            request.ContentTypeHeader += NewLine;
        }

        #region Parameter specific methods
        
        private static void AddBasicParameterToBodyContent(PostRequest request, BasicRequestParameter parameter)
        {
            if (!string.IsNullOrEmpty(request.Body))
            {
                request.BodyBuilder.Append("&");
            }

            request.BodyBuilder.Append(string.Format("{0}={1}", parameter.Name, parameter.Content));
        }

        private static void AddTextParameterToBodyContent(PostRequest request, TextRequestParameter parameter)
        {
            request.BodyBuilder.Append(string.Format(BeginBoundaryTemplate, request.BoundaryValue));

            request.BodyBuilder.Append(string.Format(DispositionHeaderTemplate, parameter.Name)).Append(NewLine);

            if (parameter.IncludeContentTypeHeaderInTheBodySection)
            {
                request.BodyBuilder.Append(string.Format(BodyTypeHeaderTemplate, parameter.ContentType));
            }

            if (parameter.TransportAsBase64)
            {
                request.BodyBuilder.Append(string.Format(TransportEncodingHeaderTemplate, "base64"));
            }
            else
            {
                request.BodyBuilder.Append(NewLine);
            }

            request.BodyBuilder.Append(parameter.Content).Append(NewLine);
        }

        private static void AddBinaryParameterToBodyContent(PostRequest request, BinaryRequestParameter parameter)
        {
            request.BodyBuilder.Append(string.Format(BeginBoundaryTemplate, request.BoundaryValue));

            request.BodyBuilder.Append(string.Format(DispositionHeaderTemplate, parameter.Name));

            request.BodyBuilder.Append(string.Format(FileNameParamTemplate, parameter.FileName)).Append(NewLine);

            request.BodyBuilder.Append(string.Format(BodyTypeHeaderTemplate, parameter.ContentType));

            request.BodyBuilder.Append(string.Format(TransportEncodingHeaderTemplate, parameter.TransportAsBase64 ? "base64" : "binary"));

            if (parameter.TransportAsBase64)
            {
                request.BodyBuilder.Append(parameter.ContentAsBase64);
            }
            else
            {
                request.AllowBodyContentOnlyAsBytes = true;

                request.BinaryParamsContent.Add(parameter.ContentAsBytes);

                request.BodyBuilder.Append(PostRequest.BinaryDataPlaceHolder);
            }

            request.BodyBuilder.Append(NewLine);
        } 

        #endregion
    }
}
